/*
 * CRtspServer.cpp
 *
 *  Created on: 2016年1月17日
 *      Author: terry
 */

#include "CRtspServer.h"

#include "RTSPServer.hh"
#include "BasicUsageEnvironment.hh"
#include "H264MediaSubsession.h"
#include "AudioMediaSubsession.h"
#include "H265MediaSubsession.h"
#include "Socket.h"

#include "Application.h"
#include "CLog.h"
#include "RtpPort.h"
#include "TSafeStr.h"
#include "StopWatch.h"


namespace av
{



class CUsageEnvironment : public BasicUsageEnvironment
{
public:
	CUsageEnvironment(TaskScheduler& scheduler):
		BasicUsageEnvironment(scheduler)
	{
	}

};

class CServerMediaSession : public ServerMediaSession
{
public:
	CServerMediaSession(UsageEnvironment& env, char const* streamName, RtspSessionListener* listener):
		ServerMediaSession(env, streamName, streamName, streamName, false, NULL),
		m_name(streamName),
        m_listener(listener)
	{
        m_listener->onCreateSession(m_name);
	}

    ~CServerMediaSession()
    {
        m_listener->onDestroySession(m_name);
    }

    virtual void onClientExit() 
    { 
        if (referenceCount() == 0)
        {
            deleteWhenUnreferenced() = true;
        }
    }

	std::string  m_name;
	MediaFormat	 m_format;
    RtspSessionListener*    m_listener;

};

class Server : public RTSPServer
{
public:
	static Server* createNew(UsageEnvironment& env, Port ourPort,
	                      UserAuthenticationDatabase* db, RtspSessionListener* listener)
	{
	    int ourSocket = -1;

	    do
	    {
	        int ourSocket = setUpOurSocket(env, ourPort);
	        if (ourSocket == -1)
            {
                CLog::warning("can not setup socket. port:%d\n", ourPort.num());
	            break;
            }

            comn::Socket socket(ourSocket);
            socket.setReuse();
            socket.setNoLinger(true);
            socket.detach();

	        unsigned int reclamationTestSeconds = 60;
	        Server* pServer = new Server(env, ourSocket, ourPort,
	                db, reclamationTestSeconds, listener);
	        return pServer;
	    } while (0);

	    if (ourSocket != -1) ::closeSocket(ourSocket);

	    return NULL;
	}

public:
	Server(UsageEnvironment& env, int ourSocket, Port ourPort, 
        UserAuthenticationDatabase* db, unsigned seconds, RtspSessionListener* listener):
		RTSPServer(env, ourSocket, ourPort, db, seconds),
            m_listener(listener)
	{
        m_sessionCounter = 0;
	}

	virtual ~Server()
	{
	}

    virtual ServerMediaSession* lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession)
	{
        comn::StopWatch watch;

		ServerMediaSession* sms = RTSPServer::lookupServerMediaSession(streamName);
		if (sms)
		{
            if (isFirstLookupInSession)
            {
			    if (!Application::instance().getChannelTable().exists(streamName))
			    {
				    removeServerMediaSession(streamName);
                    CLog::debug("RTSPServer remove media session for channel not exist. %s\n", streamName);
                    sms = NULL;
			    }
            }

            if (watch.elapse() > 50)
            {
                CLog::warning("lookupServerMediaSession consume too much time. %d\n", watch.elapse());
            }

			return sms;
		}

		sms = createMediaSession(streamName);

		if (sms)
		{
			addServerMediaSession(sms);
		}

        if (watch.elapse() > 50)
        {
            CLog::warning("lookupServerMediaSession consume too much time. %d\n", watch.elapse());
        }

		return sms;
	}

	ServerMediaSession* createMediaSession(const std::string& streamName)
	{
        m_listener->onRequestSession(streamName);
        if (!Application::instance().getChannelTable().exists(streamName))
        {
            return NULL;
        }

		CServerMediaSession* sms = NULL;
        sms = createLiveMediaSession(streamName);

        if (sms)
        {
            //sms->deleteWhenUnreferenced() = true;
        }
	    return sms;
	}

	CServerMediaSession* createLiveMediaSession(const std::string& streamName)
	{
		av::CasterChannelTable& chlTable = Application::instance().getChannelTable();
		av::CasterChannelPtr chl = chlTable.findWithName(streamName);

		if (!chl)
		{
            CLog::debug("RTSPServer media session can not be created for channel not exist. %s\n", streamName.c_str());
			return NULL;
		}

        m_sessionCounter ++;

		uint16_t initRtpPort = getInitRtpPort();
		CServerMediaSession* sms = new CServerMediaSession(envir(), streamName.c_str(), m_listener);

		MediaFormat fmt;
		chl->getMediaFormat(fmt);

		sms->m_format = fmt;

		if (fmt.m_codec == MEDIA_CODEC_H264)
		{
			H264MediaSubsession* subsession = new H264MediaSubsession(envir(), initRtpPort, chl, streamName);
			sms->addSubsession(subsession);
		}
		else if (fmt.m_codec == MEDIA_CODEC_HEVC)
		{
			H265MediaSubsession* subsession = new H265MediaSubsession(envir(), initRtpPort, chl, streamName);
			sms->addSubsession(subsession);
		}

		if ((fmt.m_audioCodec == MEDIA_CODEC_AAC) ||
				(fmt.m_audioCodec == MEDIA_CODEC_G711A) ||
				(fmt.m_audioCodec == MEDIA_CODEC_G711U))
		{
			AudioMediaSubsession* subsession = new AudioMediaSubsession(envir(), initRtpPort, chl, streamName);
			sms->addSubsession(subsession);
		}
		return sms;
	}


	uint16_t getInitRtpPort()
	{
        return RtpPort::make();
	}

    bool closeSession(const std::string& name)
    {
        ServerMediaSession* sms = RTSPServer::lookupServerMediaSession(name.c_str());
        if (!sms)
        {
            return false;
        }

        removeServerMediaSession(name.c_str());
        return true;
    }


private:
    int m_sessionCounter;   /// 会话次数
    RtspSessionListener* m_listener;

};




CRtspServer::CRtspServer():
		m_watchExit(),
        m_callback(),
        m_context()
{

}

CRtspServer::~CRtspServer()
{
    stop();
}

int CRtspServer::start(const char* ip, int port)
{
	if (isRunning())
	{
		stop();
	}


	m_scheduler.reset(BasicTaskScheduler::createNew());
	m_env.reset(new CUsageEnvironment(*m_scheduler));
	m_server.reset(Server::createNew(*m_env, port, NULL, this));

	if (!m_server)
	{
		return EEXIST;
	}

	comn::Thread::start();

	return 0;
}

void CRtspServer::stop()
{
	if (isRunning())
	{
		comn::Thread::stop();
	}

	m_server.reset();
	m_scheduler.reset();
	m_env.reset();
}

bool CRtspServer::isStarted()
{
	return isRunning();
}

bool CRtspServer::closeSession(const std::string& name)
{
    if (!m_server)
    {
        return false;
    }
    return m_server->closeSession(name);
}

int CRtspServer::run()
{
	while (!m_canExit)
	{
		m_env->taskScheduler().doEventLoop(&m_watchExit);
	}

	return 0;
}

void CRtspServer::doStop()
{
	m_watchExit = 1;

    if (m_env)
    {
        m_env->taskScheduler().triggerEvent(EventTriggerId(), 0);
    }
	
}

void CRtspServer::setCallback(RtspCasterEventCallback cb, void* context)
{
    m_callback = cb;
    m_context = context;
}

void CRtspServer::onRequestSession(const std::string& name)
{
    comn::AutoCritSec lock(m_cs);

    if (m_callback)
    {
        RtspCasterEvent event;
		memset(&event, 0, sizeof(event));
        event.type = CASTER_SESSION_REQUEST;
        comn::copyStr(event.name, name);
        (*m_callback)(&event, m_context);
    }
    else
    {
        CLog::warning("CRtspServer. callback is null.\n");
    }
}

void CRtspServer::onCreateSession(const std::string& name)
{
    comn::AutoCritSec lock(m_cs);

    if (m_callback)
    {
        RtspCasterEvent event;
		memset(&event, 0, sizeof(event));
        event.type = CASTER_SESSION_CREATE;
        comn::copyStr(event.name, name);
        (*m_callback)(&event, m_context);
    }
}

void CRtspServer::onDestroySession(const std::string& name)
{
    comn::AutoCritSec lock(m_cs);
    if (m_callback)
    {
        RtspCasterEvent event;
		memset(&event, 0, sizeof(event));
        event.type = CASTER_SESSION_DESTROY;
        comn::copyStr(event.name, name);
        (*m_callback)(&event, m_context);
    }
}


} /* namespace av */
